package xuan.gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.JCheckBox;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import xuan.lib.*;

import org.roblybarger.*;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JSplitPane;
import java.awt.Color;
import javax.swing.BoxLayout;
import java.awt.Insets;

/**
 * main frame of merge interface
 * - display operations, document tree and document content
 * - reception and respond to user actions
 * 
 * @author xuan
 *
 */
@SuppressWarnings("serial")
public class InteractiveMergePanel extends JPanel {

	private JTree docTree;
	private XMLTreeModel model;
	private List<JCheckBox> opList;
	private String strSource="";
	private String strMerged="";
	private String strEditLog="";
	public Document source;
	public Document merged;
	private EditLog editlog;
	private Merge merge;
	private JScrollPane operationsScrollPane;
	private JPanel operationsPanel;
	private JScrollPane textScrollPane;
	private JEditorPane textPane;
	private JSplitPane verticalSplitPane;
	private JSplitPane horizontalSplitPane;	
	private JFrame parent;
	private int lastOp=-1;
	
	
	public InteractiveMergePanel(JFrame parent) {
		this.parent = parent;
		
		setMinimumSize(new Dimension(800, 600));
		setPreferredSize(new Dimension(900, 800));
		setLayout(new BorderLayout());				
		
		operationsPanel = new JPanel();
		operationsPanel.setAutoscrolls(true);
		operationsPanel.setBackground(Color.WHITE);
		
		FlowLayout flowLayout = (FlowLayout) operationsPanel.getLayout();
		flowLayout.setAlignment(FlowLayout.LEFT);
		
		verticalSplitPane = new JSplitPane();
		verticalSplitPane.setDividerSize(5);
		
		operationsPanel.setLayout(new javax.swing.BoxLayout(operationsPanel, javax.swing.BoxLayout.Y_AXIS));
		operationsScrollPane = new JScrollPane(operationsPanel);
		operationsScrollPane.setAutoscrolls(true);
		operationsScrollPane.setPreferredSize(new Dimension(300, 400));
		
		model = new XMLTreeModel();
		docTree = new JTree();
		docTree.setModel(model);
		docTree.setShowsRootHandles(true);
		docTree.setEditable(false);
		
		JScrollPane treePane = new JScrollPane(docTree);
		treePane.setAutoscrolls(true);
		treePane.setPreferredSize(new Dimension(500, 400));
		
		JLabel lblDocumentTree = new JLabel("Document Tree");
		lblDocumentTree.setBackground(Color.WHITE);
		treePane.setColumnHeaderView(lblDocumentTree);
		
		verticalSplitPane.setLeftComponent(operationsScrollPane);
		
		verticalSplitPane.setRightComponent(treePane);
		
		textScrollPane = new JScrollPane();
		textScrollPane.setAutoscrolls(true);
		textScrollPane.setPreferredSize(new Dimension(800, 400));
		
		textPane = new JEditorPane();
		textPane.setEditable(false);
		textPane.setContentType("text/html");
		textPane.setEditorKit(new HTMLEditorKit());
		
		//make intern link clickable and interactive
		textPane.addHyperlinkListener(new HyperlinkListener() {
			public void hyperlinkUpdate(HyperlinkEvent e) {
				if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
					JEditorPane src = (JEditorPane)e.getSource();
					if (e.getDescription().startsWith("#")) {
						src.scrollToReference(e.getDescription().substring(1));
					}
				}
				
			}
		});
		
		//adjust size of text editor
		textScrollPane.setViewportView(textPane);		
		textScrollPane.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
            	textPane.setSize(new Dimension(
            			textScrollPane.getWidth()-5, 
            			textScrollPane.getHeight()-5));
            }
		});
		
		JLabel lblTextNode = new JLabel("Text Content");
		lblTextNode.setBackground(Color.WHITE);
		textScrollPane.setColumnHeaderView(lblTextNode);
		
		horizontalSplitPane = new JSplitPane();
		horizontalSplitPane.setPreferredSize(new Dimension(800, 800));
		horizontalSplitPane.setDividerSize(5);
		horizontalSplitPane.setMinimumSize(new Dimension(800, 800));
		horizontalSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
		add(horizontalSplitPane, BorderLayout.NORTH);
		horizontalSplitPane.setTopComponent(verticalSplitPane);
		horizontalSplitPane.setBottomComponent(textScrollPane);
		verticalSplitPane.setDividerLocation(300);		
		horizontalSplitPane.setDividerLocation(400);
			
		//by clicking on a tree cell, its text content will be displayed within text editor
		docTree.addTreeSelectionListener(new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent e) {
				Object lpc = e.getPath().getLastPathComponent();
				if (lpc instanceof XMLTreeNode) {															
					textPane.setText(DocUtils.XML2HTML(((XMLTreeNode)lpc).getElement()));
					textPane.setCaretPosition(0);
				}
			}
		});
	
		load(new String[]{"/home/xuan/workspace/3DMBetaTest/withoutInlineTags1.section","/home/xuan/workspace/3DMBetaTest/withoutInlineTags3.section","/home/xuan/workspace/3DMBetaTest/edit.log"});
	}
	
	
	/**
	 * load interface with data from merge model
	 * 
	 * @param args source file, target file and edit log
	 */
	public void load(String[] args){
		if(args!=null && args.length==3){
			strSource = args[0];
			strMerged = args[1];
			strEditLog = args[2];
		}
		
		if(strSource.compareTo("")==0 || strMerged.compareTo("")==0 || strEditLog.compareTo("")==0){
			return;
		}
		
		merge = new Merge(new String[]{strSource,strMerged,strEditLog});
		source = merge.getSource();
		merged = merge.getMerged();		
		editlog = merge.geteLog();
		model.setDocument(source);
		setOpeartionsPane();
		docTree.setCellRenderer(new TreeCellCustomRenderer(null, null,null,false));
	}


	/**
	 * display all operations
	 */
	public void setOpeartionsPane() {
		List<Operation> edits = editlog.get();
		opList = new LinkedList<JCheckBox>();
		operationsPanel.removeAll();
		
		for(int i=0;i<edits.size();i++){
			Operation op = edits.get(i);
			String str=op.toString();
			JCheckBox chckbxNewCheckBox = new JCheckBox(str);
			chckbxNewCheckBox.setBackground(Color.WHITE);			
			
			opList.add(chckbxNewCheckBox);	
			operationsPanel.add(chckbxNewCheckBox);
			
			chckbxNewCheckBox.addItemListener(new CheckedItem() );
			if(!op.isEnabled()){
				chckbxNewCheckBox.setEnabled(false);				
			}
		}
		
		operationsPanel.repaint();
		operationsPanel.revalidate();
	}

	public void undo(){
		if(lastOp!=-1 && opList.get(lastOp).isSelected() && opList.get(lastOp).isEnabled()){
			merge.undoAPreview(editlog.getOperation(lastOp));
			opList.get(lastOp).setSelected(false);
		}
		
		int index = merge.undo();
		if(index != -1){
			opList.get(index).setSelected(false);
		}
		refreshOperations();
	}
	
	/**
	 * save dom tree in XML file with given name
	 * @param fileName file name
	 */
	public void save(String fileName){
		try{
			DocUtils.exportXMLFile(source,"",fileName);			
		}
		catch(Exception e1){
			e1.printStackTrace();
		}
	}

	@Override
	public void removeAll(){
		operationsPanel.removeAll();
		textPane.removeAll();
		docTree.removeAll();
	}

	private void preview(Operation op) {
		merge.preview(op);
		model.reload();
		docTree.collapseRow(0);
		TreeCellCustomRenderer renderer;
		Object insert, delete, update;
		
		switch(op.getType()){
		case INSERT:
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				insert = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(insert, null,null,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);							
			}
			else{
				insert = new XMLTreeNode((Element)op.getSourceNode());
				renderer = new TreeCellCustomRenderer(insert, null,null, false);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			TreeUtils.expandOneNode(docTree, (XMLTreeNode)insert);
			break;
		case DELETE:
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				delete = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(null, delete,null,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);		
			}
			else{
				delete = new XMLTreeNode((Element)op.getSourceNode());
				renderer = new TreeCellCustomRenderer(null,delete,null,false);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			TreeUtils.expandOneNode(docTree, (XMLTreeNode)delete);
			break;
		case UPDATE:
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				update = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(null, null,update,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			else{
				update = new XMLTreeNode((Element)op.getTempNode());				
				renderer = new TreeCellCustomRenderer(null, null,update,false);
				docTree.setCellRenderer(renderer);
			}
			TreeUtils.expandOneNode(docTree, (XMLTreeNode)update);
			break;
		case MOVE:
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				insert = new XMLTreeNode((Element)op.getTargetNode());
				delete = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(insert,delete,null,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			else{
				insert = new XMLTreeNode((Element)op.getTempNode());
				delete = new XMLTreeNode((Element)op.getSourceNode());				
				renderer = new TreeCellCustomRenderer(insert, delete,null,false);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			
			TreeUtils.expandTwoNodes(docTree, (XMLTreeNode)insert, (XMLTreeNode)delete);			
			break;
		default://COPY
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				insert = new XMLTreeNode((Element)op.getTargetNode());
				update = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(insert,null,update,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(op.getSourceNode().getTextContent());
				textPane.setForeground(null);
			}
			else{
				insert = new XMLTreeNode((Element)op.getTempNode());
				update = new XMLTreeNode((Element)op.getSourceNode());
				renderer = new TreeCellCustomRenderer(insert, null,update,false);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
		}
		
		refreshOperations();
		
//		List<Operation> deps = op.getDeps(false);
//		for(int i=0;i<deps.size();i++){
//			Operation op2 = deps.get(i);
//			int index = editlog.find(op2);
//			opList.get(index).setEnabled(true);
//		}
	}

	private void refreshOperations() {
		JCheckBox checkBox;
		boolean finished=true;
		
		for(int i=0; i< opList.size();i++){
			checkBox = opList.get(i);
			checkBox.setText(editlog.getOperation(i).toString());
			if(editlog.getOperation(i).isEnabled()){
				checkBox.setEnabled(true);
			}
			else{
				checkBox.setEnabled(false);
			}
			if(editlog.getOperation(i).isExecuted()){
				checkBox.setEnabled(false);
			}
			finished = finished && editlog.getOperation(i).isExecuted();
		}
	}
	
	private void removeFromOperationsPane(Operation op, Boolean withDependedOperation) {
		List<Integer> indexList = new LinkedList<Integer>();
		int index = editlog.find(op);
		indexList.add(index);
		
		if(withDependedOperation){
			List<Operation> allDependedOperations = op.getDeps(true);
			for(int i=0;i<allDependedOperations.size();i++){
				Operation op2 = allDependedOperations.get(i);
				index = editlog.find(op2);
				indexList.add(index);
			}
		}
		
		Collections.sort(indexList);
		Collections.reverse(indexList);
		for(int j=0;j<indexList.size();j++){
			opList.remove((int)indexList.get(j));
			operationsPanel.remove(indexList.get(j));
		}
		operationsPanel.repaint();
		operationsPanel.revalidate();
	}

	/**
	 * reply user action checking an operation
	 *
	 */
	class CheckedItem implements ItemListener{
		public void itemStateChanged(ItemEvent itemEvent) {
//			AbstractButton abstractButton = (AbstractButton)itemEvent.getSource();
	        int state = itemEvent.getStateChange();
	       
	        int indexOp = operationsPanel.getComponentZOrder((Component) itemEvent.getItem());	        	
        	Operation op = editlog.getOperation(indexOp);        	        	
        	
	        if (state == ItemEvent.SELECTED) {
	        	if(lastOp!=-1 && lastOp!=indexOp && !editlog.getOperation(lastOp).isExecuted()){
//	        		opList.get(lastOp).setEnabled(false);
//	        		merge.execute(editlog.getOperation(lastOp));
	        		merge.undoAPreview(editlog.getOperation(lastOp));
	        		opList.get(lastOp).setSelected(false);
	        		refreshOperations();
	        	}
	        	
	        	preview(op);
	        	lastOp = indexOp;
	        	ConfirmPanel confirmPanel = new ConfirmPanel(op);
	    		operationsScrollPane.setColumnHeaderView(confirmPanel);	        	
	        }
	        if(state == ItemEvent.DESELECTED){
	        	merge.undoAPreview(op);
				model.reload();
	        }
		}
	}

	/**
	 * confirm panel get choice from user
	 * - yes : apply an operation
	 * - no : ignore an operation and all its dependents operations
	 * - cancel to go back later
	 */
	class ConfirmPanel extends JPanel implements ActionListener{
		private JButton btnApply;
		private JButton btnIgnore;
		private JButton btnCancel;
		private Operation operation;
		
		public ConfirmPanel(Operation op) {
			setBackground(new Color(173, 216, 230));
			setLayout(new BoxLayout(this, BoxLayout.X_AXIS));		
			JLabel lblNewLabel = new JLabel("Confirm ?    ");
			lblNewLabel.setMinimumSize(new Dimension(40, 15));
			lblNewLabel.setPreferredSize(new Dimension(40, 15));
			add(lblNewLabel);		
			btnApply = new JButton("Apply");
			btnApply.setMargin(new Insets(2, 5, 2, 5));
			btnApply.addActionListener(this);
			add(btnApply);
			JLabel label = new JLabel("  ");
			add(label);		
			btnIgnore = new JButton("Ignore");
			btnIgnore.setMargin(new Insets(2, 5, 2, 5));		
			JLabel label_1 = new JLabel("  ");
			btnIgnore.addActionListener(this);
			add(btnIgnore);
			add(label_1);		
			btnCancel = new JButton("Cancel");
			btnCancel.setMargin(new Insets(2, 5, 2, 5));
			btnCancel.addActionListener(this);
			add(btnCancel);
			
			operation = op;
		}
		
		public void actionPerformed(ActionEvent evt) {
			Object button = evt.getSource();
//			merge.execute(operation);
			
			if (button == btnApply) {
				confirmOperation(0);
			}
			else if(button == btnIgnore){				
				confirmOperation(1);
			}
			else{
				confirmOperation(2);
			}
			
			this.removeAll();
			this.repaint();
			this.revalidate();
		}
		
		private void confirmOperation(int selection) {
			switch(selection){
			case 0://Yes
				merge.execute(operation);
		    	refreshOperations();
		    	model.reload();
				break;
			case 1://No, ignore this operation
				merge.undoAPreview(operation);
				int option = JOptionPane.showConfirmDialog(parent, merge.alertRemoveMessage(operation), "Attention", JOptionPane.YES_NO_OPTION);
				if(option==0){ 
					removeFromOperationsPane(operation,true);
					merge.removeOutOfList(operation);
				}
				else{
					opList.get(editlog.find(operation)).setSelected(false);
				}
				
				model.reload();
				break;
			default://Cancel
				merge.undoAPreview(operation);
				model.reload();
		    	opList.get(editlog.find(operation)).setSelected(false);
			}
			docTree.setCellRenderer(new TreeCellCustomRenderer(null, null, null, false));
		
			textPane.setText(DocUtils.XML2HTML(source.getDocumentElement()));
			textPane.setCaretPosition(0);	
		}
		
//		private void confirmOperation(int selection) {
//			switch(selection){
//			case 0://Yes
//				merge.execute();
//		    	refreshOperations();
//		    	model.reload();
//				break;
//			case 1://No, ignore this operation
//				merge.ignore();
//				refreshOperations();
//				model.reload();
//				break;
//			default://Cancel
//				
//			}
//			docTree.setCellRenderer(new TreeCellCustomRenderer(null, null, null, false));
//		}
	}
}
